import Watcher from "./watcher";

export default class Compile{
    /**
     *
     * @param el
     * @param vm mvvm实例
     */
    constructor(el, vm) {
        this.el = this.isElementNode(el)?el:document.querySelector(el);
        this.vm = vm;
        if(this.el){
            // 如果能获取到这个元素，才开始编译
            // 1. 先把真实的DOM移到内存中 fragment
            let fragment = this.node2fragment(this.el);
            // 2. 编译 => 提取想要的元素节点 v-model 和文本节点 {{}}
            this.compile(fragment);
            // 3. 把编译好的fragment放回页面
            this.el.appendChild(fragment);
        }
    }

    // 辅助方法
    isElementNode(node){
        return node.nodeType === 1;
    }

    isDirective(name){
        return name.includes('v-');
    }

    // 核心方法
    compileElement(node){
        // 带v-model
        let attrs = node.attributes; // 取出当前节点的属性
        Array.from(attrs).forEach(attr=>{
            // 判断属性名字是否包含v- // [v, ]
            let attrName = attr.name;
            if(this.isDirective(attrName)){
                // 取到对应的值放到节点中
                let expr = attr.value;
                let [, type] = attrName.split('-');
                // node this.vm.$data expr
                CompileUtil[type](node, this.vm, expr);
            }
        })
    }

    compileText(node){
        // 带{{}}
        let expr = node.textContent; // 去文本中的内容
        let reg = /\{\{([^}]+)\}\}/g;
        if(reg.test(expr)){
            // node this.vm.$data text
            CompileUtil['text'](node, this.vm, expr);
        }
    }

    compile(fragment){
        let childNodes = fragment.childNodes;
        Array.from(childNodes).forEach(node=>{
            if(this.isElementNode(node)){
                // 元素节点，还需要深入检查
                // 需要编译元素
                this.compileElement(node);
                this.compile(node);
            }else {
                // 文本节点
                // 需要编译文本
                this.compileText(node);
            }
        })
    }

    node2fragment(el){ // 需要将el中的内容放到内存中
        // 文档碎片
        let fragment = document.createDocumentFragment();
        let firstChild;
        while(firstChild = el.firstChild){
            fragment.append(firstChild);
        }
        return fragment; // 内存中的节点
    }
}

let CompileUtil = {
    getVal(vm, expr){ // 获取实例上对应的数据
        expr = expr.split('.');
        return expr.reduce((prev, next)=>{
            return prev[next];
        },vm.$data);
    },

    getTextVal(vm, expr){ // 获取编译文本后的结果
        return expr.replace(/\{\{([^}]+)\}\}/g, (...args)=>{
            return this.getVal(vm, args[1]);
        })
    },

    text(node, vm, expr){ // 文本处理
        let updateFn = this.updater['textUpdater'];
        let value = this.getTextVal(vm, expr);
        expr.replace(/\{\{([^}]+)\}\}/g, (...args)=>{
            new Watcher(vm, args[1], (newValue)=>{
                // 如果数据变化了，文本节点需要重新获取依赖的数据更新文本中的内容
                updateFn && updateFn(node, this.getTextVal(vm, expr));
            })
        })
        updateFn && updateFn(node, value);
    },

    setVal(vm, expr, value){
        expr = expr.split('.');
        // 收敛
        return expr.reduce((prev, next, currentIndex)=>{
            if(currentIndex === expr.length - 1){
                return prev[next] = value;
            }
            return prev[next];
        },vm.$data)
    },

    model(node, vm, expr){ // 输入框处理
        let updateFn = this.updater['modelUpdater'];
        // 这里应该加一个监控，数据变化了应该调用watch的callback
        new Watcher(vm, expr, (newValue)=>{
            // 当值变化后会调用cb，将新的值传递过来
            updateFn && updateFn(node, this.getVal(vm, expr));
        });
        node.addEventListener('input', (e)=>{
            let newValue = e.target.value;
            this.setVal(vm, expr, newValue);
        })
        updateFn && updateFn(node, this.getVal(vm, expr));
    },

    updater:{
        // 文本更新
        textUpdater(node, value){
            node.textContent = value;
        },
        // 输入框更新
        modelUpdater(node, value){
            node.value = value;
        }
    }
}